package api

import (
	"context"
	"demo/src/app"
	"demo/src/controller"
	"encoding/json"
	"io"
	"log"
	"net/http"
	"os/exec"
	"path/filepath"
	"strconv"
	"sync"
	"sync/atomic"
)

var taskIdGen atomic.Uint64
var bus = make(chan *string, 1024)

var listeners = make([]chan *string, 0)
var globalLock sync.Mutex

func init() {
	app.Application.Controller(new(CmdController))
	go func() {
		for {
			event := <-bus
			for _, c := range listeners {
				c <- event
			}
		}
	}()
}

type CmdController struct {
	app.CommonComponent
}

func (cc *CmdController) ToHandlers() []controller.Handler {
	return []controller.Handler{cc.execute(), cc.stop()}
}

func (cc *CmdController) stop() controller.Handler {
	return controller.GetHandler("/cmd/stop/:taskId", func(ctx map[string]any, r *http.Request, w http.ResponseWriter) {
		taskId, ok := ctx["taskId"]
		if !ok || taskId == "" {
			http.Error(w, "taskId must not be null", 400)
			return
		}
		if taskIdStr, ok := taskId.(string); ok {
			bus <- &taskIdStr
		}
	})
}

func (cc *CmdController) execute() controller.Handler {
	return controller.PostHandler("/cmd/execute", func(ctx map[string]any, r *http.Request, w http.ResponseWriter) {
		all, err := io.ReadAll(r.Body)
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), 400)
			return
		}
		var shellDto struct {
			Name   string   `json:"name"`
			Path   string   `json:"path"`
			Params []string `json:"params"`
		}
		err = json.Unmarshal(all, &shellDto)
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), 400)
			return
		}
		log.Printf("req: %#v", shellDto)
		responseController := http.NewResponseController(w)
		cancel, cancelFunc := context.WithCancel(context.Background())
		taskId := taskIdGen.Add(1)
		taskIdStr := strconv.Itoa(int(taskId))
		w.Write([]byte("taskId: " + taskIdStr + "\r\n"))
		go func() {
			q := make(chan *string, 1)
			globalLock.Lock()
			listeners = append(listeners, q)
			index := len(listeners) - 1
			globalLock.Unlock()
			for {
				select {
				case <-cancel.Done():
					break
				default:
				}
				event := <-q
				if *event == taskIdStr {
					cancelFunc()
					break
				}
			}
			globalLock.Lock()
			if len(listeners) > index {
				listeners = append(listeners[:index], listeners[index+1:]...)
			} else {
				listeners = listeners[:index]
			}
			globalLock.Unlock()
		}()
		//
		commandContext := exec.CommandContext(cancel, "sh", append([]string{filepath.Join(shellDto.Path, shellDto.Name)}, shellDto.Params...)...)
		pipe, err := commandContext.StdoutPipe()
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), 500)
			return
		}
		err = commandContext.Start()
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), 500)
			cancelFunc()
			return
		}
		go func() {
			buf := make([]byte, 512)
			for {
				read, err := pipe.Read(buf)
				if err != nil {
					log.Println(err)
					break
				}
				_, err = w.Write(buf[:read])
				if err != nil {
					log.Println(err)
					break
				}
				err = responseController.Flush()
				if err != nil {
					log.Println(err)
					break
				}
			}
		}()
		commandContext.Wait()
		cancelFunc()
	})
}
